package com.pandemicode.framework.smtp.mailer {
	import com.adobe.crypto.MD5;
	import com.pandemicode.framework.encoding.Base64;
	import flash.net.Socket;
	import flash.events.ProgressEvent;
	import flash.utils.ByteArray;
	import flash.utils.getTimer;
	import org.osflash.signals.natives.NativeSignal;
	import org.osflash.signals.Signal;
	
	public class Mailer extends Socket {
		private var _socketConnected:Signal;
		private var _mailSent:Signal;
		private var _authenticated:Signal;
		private var _socketDisconnected:Signal;
		private var _badSequence:Signal;
		private var _mailError:Signal;
		private var _socketData:NativeSignal;
		
		public function get socketConnected():Signal { return this._socketConnected; }
		public function get mailSent():Signal { return this._mailSent; }
		public function get authenticated():Signal { return this._authenticated; }
		public function get socketDisconnected():Signal { return this._socketDisconnected; }
		public function get badSequence():Signal { return this._badSequence; }
		public function get mailError():Signal { return this._mailError; }
		
		public static const NEWLINE:String = "\r\n";
		
		private var _host:String;
		
		// PNG, JPEG header values
		private static const PNG:Number = 0x89504E47;
		private static const JPEG:Number = 0xFFD8;
		
		// common SMTP server response codes
		// other codes could be added to add fonctionalities and more events
		private static const ACTION_OK:Number = 0xFA;
		private static const AUTHENTICATED:Number = 0xEB;
		private static const DISCONNECTED:Number = 0xDD;
		private static const READY:Number = 0xDC;
		private static const DATA:Number = 0x162;
		private static const BAD_SEQUENCE:Number = 0x1F7;
		
		public function Mailer(host:String = "localhost", port:int = 25) {
			super(host, port);
			this._host = host;
			
			this._socketConnected = new Signal(Response);
			this._mailSent = new Signal(Response);
			this._authenticated = new Signal(Response);
			this._socketDisconnected = new Signal(Response);
			this._badSequence = new Signal(Response);
			this._mailError = new Signal(Response);
			
			this._socketData = new NativeSignal(this, ProgressEvent.SOCKET_DATA, ProgressEvent);
			this._socketData.add(this._socketDataHandler);
		}
		
		/*
		* This method lets you authenticate, just pass a login and password
		*/
		public function authenticate(login:String, pass:String):void {
			this.writeUTFBytes("EHLO " + this._host + NEWLINE);
			this.writeUTFBytes("AUTH LOGIN" + NEWLINE);
			this.writeUTFBytes(Base64.encode(login) + NEWLINE);
			this.writeUTFBytes(Base64.encode(pass) + NEWLINE);
			this.flush();
		}
		
		public function sendMessage(message:EmailMessage):void {
			try {
				this.writeUTFBytes(message.content);
				this.flush();
			} catch(e:Error) {
				trace("Error : Socket error, please check the SendMessage() method parameters");
				trace("Arguments : " + arguments);
				trace(e);
			}
		}
		
		/*
		* This method is used to send emails with attached files and HTML
		* takes an incoming Bytearray and convert it to base64 string
		* for instance pass a JPEG ByteArray stream to get a picture attached in the mail ;)
		*/
		public function sendAttachedMail(from:String, dest:String, subject:String, mess:String, byteArray:ByteArray, fileName:String) :void {
			try {
				var md5Boundary:String = MD5.hash(String(getTimer()));
				var boundary:String = "--------------" + md5Boundary + NEWLINE;
				
				this.writeUTFBytes("HELO "+_host+NEWLINE);
				this.writeUTFBytes("MAIL FROM: <"+from+">"+NEWLINE);
				this.writeUTFBytes("RCPT TO: <"+dest+">"+NEWLINE);
				this.writeUTFBytes("DATA"+NEWLINE);
				this.writeUTFBytes("From: "+from+NEWLINE);
				this.writeUTFBytes("To: "+dest+NEWLINE);
				this.writeUTFBytes("Date : "+new Date().toString()+NEWLINE);
				this.writeUTFBytes("Subject: "+subject+NEWLINE);
				this.writeUTFBytes("Mime-Version: 1.0"+NEWLINE);
				this.writeUTFBytes("Content-Type: multipart/mixed; boundary="+boundary);
				this.writeUTFBytes("This is a multi-part message in MIME format."+NEWLINE);
				this.writeUTFBytes(boundary);
				this.writeUTFBytes("Content-Type: text/html; charset=UTF-8; format=flowed"+NEWLINE);
				this.writeUTFBytes(NEWLINE);
				this.writeUTFBytes(mess+NEWLINE);
				this.writeUTFBytes(boundary);
				this.writeUTFBytes(_readHeader(byteArray, fileName));
				this.writeUTFBytes("Content-Transfer-Encoding: base64" + NEWLINE);
				var base64String:String = Base64.encodeBytes(byteArray);
				this.writeUTFBytes(base64String+NEWLINE);
				this.writeUTFBytes(boundary);
				this.writeUTFBytes("."+NEWLINE);
				this.flush();
			} catch(pError:Error) {
				trace("Error : Socket error, please check the sendAttachedMail() method parameters");
				trace("Arguments : " + arguments);
			}
		}
		
		/*
		* This method is used to send HTML emails
		* just pass the HTML string to mess
		*/
		public function sendHTMLMail(from:String, dest:String, subject:String, mess:String):void {
			try {
				this.writeUTFBytes("HELO " + _host + NEWLINE);
				this.writeUTFBytes("MAIL FROM: <" + from + ">" + NEWLINE);
				this.writeUTFBytes("RCPT TO: <" + dest + ">" + NEWLINE);
				this.writeUTFBytes("DATA" + NEWLINE);
				this.writeUTFBytes("From: " + from + NEWLINE);
				this.writeUTFBytes("To: " + dest + NEWLINE);
				this.writeUTFBytes("Subject: " + subject + NEWLINE);
				this.writeUTFBytes("Mime-Version: 1.0" + NEWLINE);
				this.writeUTFBytes("Content-Type: text/html; charset=UTF-8; format=flowed" + NEWLINE);
				this.writeUTFBytes(NEWLINE);
				this.writeUTFBytes(mess + NEWLINE);
				this.writeUTFBytes("." + NEWLINE);
				this.flush();
			} catch(pError:Error) {
				trace("Error : Socket error, please check the sendHTMLMail() method parameters");
				trace("Arguments : " + arguments);
			}
		}
		
		/*
		* This method automatically detects the header of the binary stream and returns appropriate headers(jpg, png)
		* classic application/octet-stream content type is added for different kind of files
		*/
		private function _readHeader(byteArray:ByteArray, fileName:String):String {
			var sOutput:String = null;
			
			byteArray.position = 0;
			if(byteArray.readUnsignedInt() == Mailer.PNG) {
				sOutput = "Content-Type: image/png; name=" + fileName + NEWLINE;
				sOutput += "Content-Disposition: attachment filename=" + fileName + NEWLINE;
				sOutput += "Content-Location: " + fileName.split(".")[0] + NEWLINE;
				return sOutput;
			}
			
			byteArray.position = 0;
			if(byteArray.readUnsignedShort() == Mailer.JPEG) {
				sOutput = "Content-Type: image/jpeg; name=" + fileName + NEWLINE;
				sOutput += "Content-Disposition: attachment filename=" + fileName + NEWLINE;
				sOutput += "Content-Location: " + fileName.split(".")[0] + NEWLINE;
				return sOutput;
			}
				
			sOutput = "Content-Type: application/octet-stream; name=" + fileName + NEWLINE;
			sOutput += "Content-Disposition: attachment filename=" + fileName + NEWLINE;
			return sOutput;
		}
		
		// check SMTP response and dispatch proper events
		private function _socketDataHandler(e:ProgressEvent):void {
			var response:String = e.target.readUTFBytes(e.target.bytesAvailable);
			var reg:RegExp = /^\d{3}/img;
			var buffer:Array = new Array();
			var result:Object = reg.exec(response);
			while(result != null) {
				buffer.push(result[0]);
				result = reg.exec(response);
			}
			
			var smtpReturn:Number = buffer[buffer.length - 1];
			
			var smtpInfos:Response = new Response(smtpReturn, response);
			
			if (smtpReturn == Mailer.READY) this._socketConnected.dispatch(smtpInfos);
			else if (smtpReturn == Mailer.ACTION_OK && (response.toLowerCase().indexOf("queued") != -1 || response.toLowerCase().indexOf("accepted") != -1)) this._mailSent.dispatch(smtpInfos);
			else if (smtpReturn == Mailer.AUTHENTICATED) this._authenticated.dispatch(smtpInfos);
			else if (smtpReturn == Mailer.DISCONNECTED) this._socketDisconnected.dispatch(smtpInfos);
			else if (smtpReturn == Mailer.BAD_SEQUENCE) this._badSequence.dispatch(smtpInfos);
			else if (smtpReturn != Mailer.DATA) this._mailError.dispatch(smtpInfos);
		}
	}
}
